<?php

declare(strict_types=1);

namespace Zms\Validate\Rule;

use Zms\Exception\AppException;
use Zms\Exception\ValidateException;
use Zms\Unit\Is;

/**
 * 违禁词检测类
 */
class ViolateValidate extends RuleAbstract
{
    /**
     * 默认错误消息
     * @var string
     */
    protected $_error = '{label}中含有非法的词汇,{words}';

    /**
     * 需要检测的违禁词汇列表
     * @var array
     */
    protected $_words = [];

    /**
     * 设置检测模式：'warn' 或 'replace'
     * @var string
     */
    protected $_model = 'warn';

    /**
     * 设置需要检测的词汇
     *
     * @param array $words 需要检测的词汇数组
     * @return $this 当前实例
     */
    public function setWords(array $words): ViolateValidate
    {
        if (count($words) > 0) {
            foreach (array_unique($words) as $item) {
                $this->_words[] = trim($item);
            }
        }
        return $this;
    }

    /**
     * 设置检测模式
     *
     * @param string $model 模式 ('warn' 或 'replace')
     * @return $this 当前实例
     * @throws AppException 如果提供的模式不是 'warn' 或 'replace'
     */
    public function setModel(string $model): ViolateValidate
    {
        if (in_array($model, ['warn', 'replace'])) {
            $this->_model = $model;
            return $this;
        }
        throw new AppException("检测模式错误");
    }

    /**
     * 开始验证
     *
     * @param mixed $value 待验证的值
     * @param string $label 字段标签，用于替换错误信息中的占位符
     * @return bool 验证是否通过
     */
    public function validate($value, string $label = ''): bool
    {
        // 如果没有设置违禁词或输入为空，则直接返回true
        if (count($this->_words) === 0 || Is::empty($value)) {
            $this->_value = $value;
            return true;
        }

        try {
            // 调用 checkViolateWords 方法进行违禁词检测
            $this->_value = $this->checkViolateWords($value);
            return true;
        } catch (ValidateException $e) {
            // 如果捕获到 ValidateException 异常，添加错误信息并返回false
            return $this->addError($e->getMessage(), $label);
        }
    }

    /**
     * 检测违禁词
     *
     * @param mixed $value 待验证的值
     * @return array|string 处理后的值（可能已被替换为星号）
     * @throws ValidateException 如果检测模式为 'warn' 并且发现违禁词
     */
    public function checkViolateWords($value)
    {
        if (is_string($value)) {
            foreach ($this->_words as $word) {
                if (strpos($value, $word) !== false) {
                    if ($this->_model === 'warn') {
                        // 在警告模式下抛出异常
                        throw new ValidateException(str_replace('{words}', "如:‘{$word}’", $this->_error));
                    }
                    // 在替换模式下调用 dynamicStarReplace 方法
                    return $this->dynamicStarReplace($word, $value);
                }
            }
            return $value;
        }
        if (is_array($value)) {
            // 如果输入是数组，则递归处理每个元素
            foreach ($value as $key => $item) {
                $value[$key] = $this->checkViolateWords($item);
            }
            return $value;
        }
        return $value;
    }

    /**
     * 将违禁词替换为星号
     *
     * @param array $search 需要替换的违禁词列表
     * @param string $subject 主体字符串
     * @return array|string|null 替换后的内容
     */
    private function dynamicStarReplace(array $search, string $subject)
    {
        // 构建正则表达式模式
        $pattern = [];
        foreach ($search as $item) {
            // 转义正则特殊字符
            $pattern[] = '/' . preg_quote($item, '/') . '/';
        }
        // 使用 preg_replace_callback 函数进行替换
        return preg_replace_callback($pattern, function ($matches) {
            // 根据匹配内容的长度生成相应数量的星号
            return str_repeat('*', mb_strlen($matches[0]));
        }, $subject);
    }
}